﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using haptic.src.engine;
using haptic.src.engine.collisions;
using Microsoft.Xna.Framework;
using System.Diagnostics;
using haptic.src.engine.core;

namespace haptic.src.components
{
    [Serializable]
    public class cPhysics : Component
    {
        public enum CollisionEvent
        {
            E_NONE = 0,
            E_START_TOUCH,
            E_TOUCH,
            E_END_TOUCH
        }

        public cPhysics(){}
        public cPhysics(Entity oOwner) : base(oOwner) { }

        public bool IsColliding() { return m_eCollisionEvent == CollisionEvent.E_START_TOUCH || m_eCollisionEvent == CollisionEvent.E_TOUCH; }

        #region Update
        public override void Update(GameTime oTime)
        {
            float fDt = (float)oTime.ElapsedGameTime.TotalSeconds;

            Vector2 vForceSum = Vector2.Zero;

            // Gravity
            vForceSum.Y += F_GRAVITY * m_fMass;

            // Friction
            float fFrictionCoef;
            if (IsColliding())
                fFrictionCoef = m_fGroundFriction;
            else
                fFrictionCoef = m_fAirFriction;
            vForceSum -= m_vVelocity * fFrictionCoef * m_fMass;

            // Integration
            m_vVelocity += vForceSum / m_fMass * fDt;
            m_oEntity.m_vPosition += m_vVelocity * fDt;

            bool bIsColliding = false;
            m_bIsOnGround = false;
            if (oCollider != null)
            {
                m_oCollisionHits.Clear();
                for (int i = 0; i < 3; i++)
                {
                    oCollider.oCollider.SetCenter(m_oEntity.m_vPosition);
                    CollisionHit oHit = oCollider.oCollisionManager.CollideFirst(oCollider.oCollider, CollisionManager.ColliderFlags.E_DEFAULT);
                    if (oHit == null)
                        break;

                    // Position correction
                    float fSeparation = (oHit.m_vTargetContact - oHit.m_vSourceContact).Length();
                    m_oEntity.m_vPosition += oHit.m_vNormal * (fSeparation + 0.1f/*epsilon*/);

                    // Bounce
                    Vector2 vNormalVelocity = oHit.m_vNormal * Vector2.Dot(m_vVelocity, oHit.m_vNormal);
                    m_vVelocity -= (1.0f + m_fRestitution) * vNormalVelocity;

                    // Updating status
                    bIsColliding = true;
                    m_bIsOnGround |= oHit.m_vNormal.Y < -( 1.0f - F_GROUND_NORMAL_TOLERANCE );

                    m_oCollisionHits.Add(oHit);
                }
            }
            if (bIsColliding)
            {
                if (m_eCollisionEvent == CollisionEvent.E_START_TOUCH)
                    m_eCollisionEvent = CollisionEvent.E_TOUCH;
                else if (m_eCollisionEvent != CollisionEvent.E_TOUCH)
                    m_eCollisionEvent = CollisionEvent.E_START_TOUCH;
            }
            else
            {
                if (m_eCollisionEvent == CollisionEvent.E_START_TOUCH || m_eCollisionEvent == CollisionEvent.E_TOUCH)
                    m_eCollisionEvent = CollisionEvent.E_END_TOUCH;
                else
                    m_eCollisionEvent = CollisionEvent.E_NONE;
            }
        }
        #endregion

        #region Load
        public override Result Load(haptic.src.engine.Scene oGame, Microsoft.Xna.Framework.Content.ContentManager oContent)
        {
            oCollider = (cCollider)m_oEntity.GetComponent("cCollider");
            return base.Load(oGame, oContent);
        }
        #endregion

        #region DrawDebug
        public override void DrawDebug(haptic.src.engine.debug.DebugRenderer oDebugRenderer, GameTime oTime)
        {
            foreach (CollisionHit oHit in m_oCollisionHits)
            {
                oDebugRenderer.DrawVector(oHit.m_vTargetContact, oHit.m_vNormal);
            }
            base.DrawDebug(oDebugRenderer, oTime);
        }
        #endregion

        #region Constants
        [XmlIgnore]
        public float F_GRAVITY = 9.8f * 150.0f;
        private const float F_GROUND_NORMAL_TOLERANCE = 0.5f;
        #endregion

        #region Attributes
        public Vector2 m_vVelocity = Vector2.Zero;
        private cCollider oCollider;
        private CollisionEvent m_eCollisionEvent = CollisionEvent.E_NONE;
        [XmlIgnore]
        public CollisionEvent eCollisionEvent { get { return m_eCollisionEvent; } }
        public float m_fAirFriction = 0.5f;
        public float m_fGroundFriction = 6.0f;
        public float m_fMass = 70.0f;
        public float m_fRestitution = 1.0f;
        private bool m_bIsOnGround = false;
        private List<CollisionHit> m_oCollisionHits = new List<CollisionHit>();
        [XmlIgnore]
        public bool bIsOnGround { get { return m_bIsOnGround; } }
        #endregion
    }
}
